package cheme.ProcessModel.ProcessUnits;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;

import cheme.ProcessModel.AbstractParent;
import cheme.ProcessModel.IChild;
import cheme.ProcessModel.IModelElement;
import cheme.ProcessModel.ProcessUnits.Streams.AbstractStream;

/** 
 * Note that in the context of the model, for out streams (streams for which a given 
 * process unit is the source), the source unit is considered it's parent and calling
 * getParent on the stream will yield that unit. This is not the case for sink
 * (or input) units.
 */
public abstract class ProcessUnit extends AbstractParent<IChild> implements IProcessUnit
{
	/*
	 * Serialization version identifier. (Set to default for now)
	 */
	
	private static final long serialVersionUID = 1L;

	private transient IPropertyDescriptor[] descriptors = 
		new IPropertyDescriptor[] { new TextPropertyDescriptor(
			P_TEXT, "Item Text") };
	
	/*
	 * This pair of value controls how many input and output streams a PU will allow connection to.
	 * For instance a TerminalSource will allow allowedInStreams = 0 and allowedOutStreams = UNLIMITED
	 */
	
	private int allowedInStreams = UNLIMITED;
	private int allowedOutStreams = UNLIMITED;

	/*
	 * The data of the node.
	 */
	private String text;

	/*
	 * The geometry of the node.
	 */
	private Rectangle bounds;
	
	private List<IChild> inStreams = new ArrayList<IChild>();
	
	public ProcessUnit()
	{
		this(null);
	}
	
	public ProcessUnit(IModelElement parent)
	{
		super(parent);
		
		initializeBounds();
	}
	
	protected void initializeStreamCharacteristics(int allowedInStreams, int allowedOutStreams)
	{
		this.allowedInStreams = allowedInStreams;
		this.allowedOutStreams = allowedOutStreams;
	}

	protected void initializeBounds()
	{
		setBounds(new Rectangle(0, 0, 40, 40));
	}
	
	public String getText()
	{
		return text;
	}

	public void setText(String text)
	{
		if(null == this.text || !this.text.equals(text))
		{
			this.text = text;
			firePropertyChange(P_TEXT, null, text);
		}
	}
	
	public Rectangle getBounds()
	{
		return bounds.getCopy();
	}
	
	public Point getLocation()
	{
		return bounds.getTopLeft();
	}
	
	public void setLocation(Point location)
	{
		bounds.setLocation(location);
		firePropertyChange(P_CONSTRAINT, null, bounds);
	}
	
	public Dimension getSize()
	{
		return new Dimension(40,40);
	}
	
	public void setSize(Dimension size)
	{
	}

	public void setBounds(Rectangle bounds)
	{
		if(null == this.bounds || !this.bounds.equals(bounds))
		{
			this.bounds = bounds;

			firePropertyChange(P_CONSTRAINT, null, bounds);
		}
	}

	public IPropertyDescriptor[] getPropertyDescriptors()
	{
		return descriptors;
	}

	public Object getPropertyValue(Object id)
	{
		if(id.equals(P_TEXT))
		{
			return text;
		}

		return null;
	}

	public boolean isPropertySet(Object id)
	{
		if(id.equals(P_TEXT))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	public void setPropertyValue(Object id, Object value)
	{
		if(id.equals(P_TEXT))
		{
			setText((String)value);
		}
	}
	
	public boolean willAcceptOutStream()
	{
		return allowedOutStreams == UNLIMITED || getOutStreams().size() < allowedOutStreams;
	}
	
	public boolean willAcceptInStream()
	{
		return allowedInStreams == UNLIMITED || getInStreams().size() < allowedInStreams;
	}
	
	public boolean willAcceptOutStream(int additionalStreamCount)
	{
		int streamCount = getOutStreams().size();
		return allowedOutStreams == UNLIMITED || (streamCount + additionalStreamCount) <= allowedOutStreams;
	}
	
	public boolean willAcceptInStream(int additionalStreamCount)
	{
		int streamCount = getOutStreams().size();
		return allowedInStreams == UNLIMITED || (streamCount + additionalStreamCount) <= allowedInStreams;
	}
	
	public void addOutStream(AbstractStream stream)
	{
		if(getOutStreams().contains(stream))
		{
			throw new IllegalArgumentException("The stream is already connected.");
		}
		
		if(willAcceptOutStream())
		{
			/*
			 * Note that, the stream/node relationship behaves a little differently than the typical
			 * parent/child relationship usef elsewhere in the code. As such, this code does not 
			 * set the stream's parent to the unit/null upon addition/removal of the source unit as it's parent.
			 */
			getOutStreams().add(stream);
			firePropertyChange(P_SOURCE_STREAM, null, stream);
		}
	}

	public void addInStream(AbstractStream stream)
	{
		if(getInStreams().contains(stream))
		{
			throw new IllegalArgumentException("The stream is already connected.");
		}
		
		if(willAcceptInStream())
		{
			getInStreams().add(stream);
			firePropertyChange(P_SINK_STREAM, null, stream);
		}
	}

	public List<IChild> getOutStreams()
	{
		return getChildren();
	}

	public List<IChild> getInStreams()
	{
		return inStreams;
	}

	public void removeOutStream(AbstractStream stream)
	{
		if(!getOutStreams().contains(stream))
		{
			throw new IllegalArgumentException("The stream is already disconnected.");
		}
		
		/*
		 * Note that, the stream/node relationship behaves a little differently than the typical
		 * parent/child relationship usef elsewhere in the code. As such, this code does not 
		 * set the stream's parent to the unit/null upon addition/removal of the source unit as it's parent.
		 */
		getOutStreams().remove(stream);
		firePropertyChange(P_SOURCE_STREAM, stream, null);
	}

	public void removeInStream(AbstractStream stream)
	{
		if(!getInStreams().contains(stream))
		{
			throw new IllegalArgumentException("The stream is already disconnected.");
		}
		
		getInStreams().remove(stream);
		firePropertyChange(P_SINK_STREAM, stream, null);
	}
}
